# -*- coding: utf-8 -*-
# @Author: Jerry
# @Date:   2022-02-23 17:18:15
# @Last Modified by:   Jerry
# @Last Modified time: 2022-03-01 15:30:51

'''
http://www.woshicver.com
本教程中，将学习简单阈值，自适应阈值和Otsu阈值。
你将学习函数 cv.threshold 和 cv.adaptiveThreshold。
'''

# **********
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import os
rootpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
datapath = os.path.join(rootpath,'data')
imgpath = lambda name: os.path.join(datapath,name)

# **********
'''
简单阈值
概念直截了当。对于每个像素，应用相同的阈值。如果像素值小于阈值，则将其设置为0，否则将其设置为最大值。
函数 cv.threshold 用于应用阈值。
cv.threshold(src, thresh, maxval, type[, dst]) -> retval, dst
第一个参数是源图像(灰度图)。
第二个参数是阈值，用于对像素值进行分类。
第三个参数是分配给超过阈值的像素值的最大值。
OpenCV提供了不同类型的阈值，这由函数的第四个参数给出。通过使用**cv.THRESH_BINARY**类型。
所有简单的阈值类型为：
    cv.THRESH_BINARY
    cv.THRESH_BINARY_INV
    cv.THRESH_TRUNC
    cv.THRESH_TOZERO
    cv.THRESH_TOZERO_INV

    cv.THRESH_BINARY+cv.THRESH_OTSU

该方法返回两个输出。第一个是使用的阈值，第二个输出是**阈值后的图像**。
'''
def SimpleThreshold():
    img = cv.imread(imgpath('gradient.png')) # cv读取的图片为BGR
    img = cv.cvtColor(img,cv.COLOR_BGR2RGB)  # plt展示的的图片为RGB、通道定义不同，需转化
    plt.subplot(231),plt.imshow(img),plt.title('Org')
    plt.xticks([]),plt.yticks([])
    ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
    plt.subplot(232),plt.imshow(thresh1),plt.title('THRESH_BINARY')
    plt.xticks([]),plt.yticks([])
    ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
    plt.subplot(233),plt.imshow(thresh1),plt.title('THRESH_BINARY_INV')
    plt.xticks([]),plt.yticks([])
    ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
    plt.subplot(234),plt.imshow(thresh1),plt.title('THRESH_TRUNC')
    plt.xticks([]),plt.yticks([])
    ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
    plt.subplot(235),plt.imshow(thresh1),plt.title('THRESH_TOZERO')
    plt.xticks([]),plt.yticks([])
    ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
    plt.subplot(236),plt.imshow(thresh1),plt.title('THRESH_TOZERO_INV')
    plt.xticks([]),plt.yticks([])
    plt.show()


# **********
'''
https://blog.csdn.net/Friedrichor/article/details/121845103
opencv读取到的图形 用 cv.imshow和plt.show显示出来颜色效果不同
- 原因：
opencv
    cv2.imread( ) 所读取的图像格式：每个像素为[B,G,R]的形式
    cv2.imshow( ) 显示图像：按图像中每个像素为[B,G,R]的规则将图像显示出来
matplotlib
    mpimg.imread( ) 所读取的图像格式：每个像素为[R,G,B]的形式
    plt.imshow( ) 显示图像：按图像中每个像素为[R,G,B]的规则将图像显示出来
    plt.show()

- 解决方案
1. cv2 读取的是 [B,G,R] ，把它转化成顺序为 [R,G,B]，plt就能正确显示了
2. 谁读取谁显示

   plt的读取和显示
   import matplotlib.image as mpimg
   image = mpimg.imread(img_full_path),plt.imshow(image),plt.show()

   cv的读取和显示
   import cv2
   image = cv2.imread(img_full_path),cv2.imshow('img',image),cv2.waitKey(0)
3. 使用image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)转化为恢复图，可无差别
'''
def SimpleThreshold_pltshow_diff_cvshow():
    img=cv.imread(imgpath('gradient.png'),cv.COLOR_BGR2GRAY)
    cv.imshow('img',img)
    plt.imshow(img),plt.xticks([]),plt.yticks([])
    plt.show()
    cv.waitKey(0)
    cv.destroyAllWindows()



# **********
'''
自适应阈值
cv.threshold 的设置是用一个全局值作为阈值。但这并非在所有情况下都很好
例如，如果图像在不同区域具有不同的光照条件。在这种情况下，自适应阈值阈值化可以提供帮助。
在此，cv.adaptiveThreshold函数所提供的算法基于像素周围的小区域确定像素的阈值。
因此，对于同一图像的不同区域，我们获得了不同的阈值，这为光照度变化的图像提供了更好的结果。

除上述参数外，方法 cv.adaptiveThreshold 还包含三个输入参数：
cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst

adaptiveMethod: 决定阈值是如何计算的
cv.ADAPTIVE_THRESH_MEAN_C::阈值是邻近区域的平均值减去常数**C**
cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值的高斯加权总和减去常数**C**

BLOCKSIZE: 确定附近区域的大小，
C: 是从邻域像素的平均或加权总和中减去的一个常数。
'''



# **********
'''
Otsu的二值化
  在全局阈值化中，我们使用任意选择的值作为阈值。相反，Otsu的方法避免了必须选择一个值并自动确定它的情况。
  考虑仅具有两个不同图像值的图像（双峰图像），其中直方图将仅包含两个峰。一个好的阈值应该在这两个值的中间。
  类似地，Otsu的方法从图像直方图中确定最佳全局阈值。

  为此，使用了 cv.threshold 作为附加标志传递。阈值可以任意选择。
  然后，算法找到最佳阈值，该阈值作为第一输出返回。

  -- 也就是说，其图形变换仍旧是使用方法cv.threshold，但是通过算法找到了最佳阈值代入

#####
https://baike.baidu.com/item/otsu/16252828?fr=aladdin
Otsu算法是由日本学者OTSU于1979年提出的一种对图像进行二值化的高效算法。
OTSU算法（大津法或最大类间方差法）
原理：
  利用阈值将原图像分成前景，背景两个图象。
  前景：用n1,csum,m1来表示在当前阈值下的前景的点数，质量矩，平均灰度
  背景：用n2, sum-csum,m2来表示在当前阈值下的背景的点数，质量矩，平均灰度
  当取最佳阈值时，背景应该与前景差别最大，关键在于如何选择衡量差别的标准，而在otsu算法中这个衡量差别的标准就是最大类间方差，
  在本程序中类间方差用sb表示，最大类间方差用fmax

关于最大类间方差法（otsu）的性能：
  类间方差法对噪音和目标大小十分敏感，它仅对类间方差为单峰的图像产生较好的分割效果。
  当目标与背景的大小比例悬殊时，类间方差准则函数可能呈现双峰或多峰，此时效果不好，但是类间方差法是用时最少的。
最大类间方差法（otsu）的公式推导：
  记t为前景与背景的分割阈值，前景点数占图像比例为w0，平均灰度为u0；背景点数占图像比例为w1，平均灰度为u1。
  则图像的总平均灰度为：u=w0*u0+w1*u1。
  前景和背景图象的方差：g=w0*(u0-u)*(u0-u)+w1*(u1-u)*(u1-u)=w0*w1*(u0-u1)*(u0-u1),此公式为方差公式。
  可参照概率论课本上面的g的公式也就是下面程序中的sb的表达式。
  当方差g最大时，可以认为此时前景和背景差异最大，此时的灰度t是最佳阈值sb = w0*w1*(u1-u0)*(u0-u1)

#####
以下示例。输入图像为噪点图像。
  在第一种情况下，采用值为127的全局阈值。
  在第二种情况下，直接采用Otsu阈值法。
  在第三种情况下，首先使用5x5高斯核对图像进行滤波以去除噪声，然后应用Otsu阈值处理。
了解噪声滤波如何改善结果。
'''
def otsu_test():
    img = cv.imread(imgpath('noisy1.png'),0)
    #全局阈值
    ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
    #Otsu阈值,此时不再给定全局阈值(设置为0)，而是在thresh的模式中指定方式
    ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
    #高斯滤波后再采用Otsu阈值
    # cv.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) -> dst
    # ksize Gaussian kernel size.
    # ksize.width and ksize.height can differ but they both must be positive and odd.
    # ksize.width和ksize.height都必须是大于0的奇数
    blur = cv.GaussianBlur(img,(3,5),0) #得到经过高斯滤波后的图(高斯模糊)
    ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
    # 绘制所有图像及其直方图
    images = [img, 0, th1,
              img, 0, th2,
              blur, 0, th3]
    titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
              'Original Noisy Image','Histogram',"Otsu's Thresholding",
              'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
    for i in xrange(3):
        plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
        plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
        plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256) #画图图形的柱状图
        plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
        plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
        plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
    plt.show()

'''
cv.cvtColor
resieze
move
rotation
affine
perspective
threshold
otsu
gaussian blur
'''

if __name__ == '__main__':
    # SimpleThreshold()
    # SimpleThreshold_pltshow_diff_cvshow()
    otsu_test()

